package com.tiecode.pastejava;

import com.rapid.util.reflect.Reflect;
import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.BreakTree;
import com.sun.source.tree.CaseTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ConditionalExpressionTree;
import com.sun.source.tree.ContinueTree;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.ForLoopTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.IfTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.InstanceOfTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ParenthesizedTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.SwitchTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TryTree;
import com.sun.source.tree.UnaryTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.tree.WhileLoopTree;
import com.sun.source.util.TreeScanner;
import com.sun.tools.javac.tree.JCTree;
import com.tiecode.compiler.api.process.Elements;
import com.tiecode.compiler.toolchain.symbol.Symbol;

import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;

import javax.lang.model.element.Modifier;

public class Java2TiecodePrinter extends TreeScanner<Void, StringBuilder> {

    private Elements elements;
    private int tab;
    private boolean isMethodOrClass = false;
    private List<VariableTree> classVars = new ArrayList<>();
    private List<? extends VariableTree> methodParams;

    public Java2TiecodePrinter(Elements elements, int tab) {
        this.elements = elements;
        this.tab = tab;
    }

    private void printTab(StringBuilder builder) {
        for (int i = 0; i < tab; i++) {
            builder.append("\t");
        }
    }

    private void println(StringBuilder builder) {
        builder.append("\n");
    }

    @Override
    public Void visitImport(ImportTree node, StringBuilder builder) {
        builder.append("@导入Java(\"").append(node.getQualifiedIdentifier()).append("\")");
        return null;
    }

    @Override
    public Void visitClass(ClassTree node, StringBuilder builder) {
        classVars.clear();
        if (node.getSimpleName().contentEquals("__Root")) {
            scan(node.getMembers(), builder);
        } else {
            isMethodOrClass = true;
            builder.append("@输出名(\"").append(node.getSimpleName()).append("\")");
            println(builder);
            builder.append("类 ").append(node.getSimpleName());
            Tree extendsClause = node.getExtendsClause();
            if (extendsClause != null) {
                String tiecodeType = getTiecodeType(extendsClause.toString());
                builder.append(" : ").append(tiecodeType);
            }
            println(builder);
            tab++;
            for (Tree member : node.getMembers()) {
                if (member.getKind() == Tree.Kind.VARIABLE) {
                    VariableTree variableTree = (VariableTree) member;
                    classVars.add(variableTree);
                }
                scan(member, builder);
                println(builder);
            }
            tab--;
            println(builder);
            printTab(builder);
            builder.append("结束 类");
        }
        return null;
    }

    @Override
    public Void visitMethod(MethodTree node, StringBuilder builder) {
        if (node.getName().contentEquals("<init>")) {
            return null;
        }
        isMethodOrClass = true;
        printTab(builder);
        builder.append("@输出名(\"").append(node.getName()).append("\")");
        println(builder);
        printTab(builder);
        if (node.getModifiers().getFlags().contains(Modifier.STATIC)) {
            builder.append("@静态");
            println(builder);
            printTab(builder);
        }
        builder.append("方法 ").append(node.getName()).append("(");
        List<? extends VariableTree> parameters = node.getParameters();
        methodParams = parameters;
        if (parameters != null) {
            for (int i = 0; i < parameters.size(); i++) {
                if (i > 0) {
                    builder.append(", ");
                }
                VariableTree variableTree = parameters.get(i);
                builder.append(variableTree.getName());
                builder.append(": ");
                String tiecodeType = getTiecodeType(variableTree.getType().toString());
                builder.append(tiecodeType);
            }
        }
        builder.append(")");
        Tree returnType = node.getReturnType();
        if (returnType != null && !"void".equals(returnType.toString())) {
            String tiecodeType = getTiecodeType(returnType.toString());
            builder.append(": ").append(tiecodeType);
        }
        tab++;
        if (isMethodOrClass) {
            println(builder);
            printTab(builder);
            builder.append("@code");
            println(builder);
            StringWriter writer = new StringWriter();
            MyPretty pretty = new MyPretty(writer, true, classVars, methodParams);
            Reflect reflect = Reflect.on(pretty);
            for (int i = 0; i < tab; i++) {
                reflect.call("indent");
            }
            try {
                pretty.printStats(((JCTree.JCBlock) node.getBody()).getStatements());
            } catch (IOException e) {
                e.printStackTrace();
            }
            builder.append(writer);
            printTab(builder);
            builder.append("@end");
        } else {
            scan(node.getBody(), builder);
        }
        tab--;
        println(builder);
        printTab(builder);
        builder.append("结束 方法");
        return null;
    }

    @Override
    public Void visitBlock(BlockTree node, StringBuilder builder) {
        List<? extends StatementTree> statements = node.getStatements();
        for (int i = 0; i < statements.size(); i++) {
            StatementTree statement = statements.get(i);
            println(builder);
            printTab(builder);
            scan(statement, builder);
        }
        return null;
    }

    @Override
    public Void visitVariable(VariableTree node, StringBuilder builder) {
        printTab(builder);
        ExpressionTree initializer = node.getInitializer();
        if (isDimensionedArray(initializer)) {
            String tiecodeType = getTiecodeType(node.getType().toString(), true);
            builder.append("变量 ").append(node.getName()).append(": ").append(tiecodeType);
            NewArrayTree newArrayTree = (NewArrayTree) initializer;
            for (ExpressionTree dimension : newArrayTree.getDimensions()) {
                builder.append("[");
                scan(dimension, builder);
                builder.append("]");
            }
            if (newArrayTree.getInitializers() != null) {
                builder.append(" = ");
                scan(initializer, builder);
            }
        } else {
            String tiecodeType = getTiecodeType(node.getType().toString());
            builder.append("变量 ").append(node.getName()).append(": ").append(tiecodeType);
            if (initializer != null) {
                builder.append(" = ");
                scan(initializer, builder);
            }
        }
        return null;
    }

    private boolean isDimensionedArray(ExpressionTree tree) {
        if (tree == null) {
            return false;
        }
        if (tree.getKind() == Tree.Kind.NEW_ARRAY) {
            NewArrayTree newArrayTree = (NewArrayTree) tree;
            List<? extends ExpressionTree> dimensions = newArrayTree.getDimensions();
            if (dimensions != null && !dimensions.isEmpty()) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Void visitWhileLoop(WhileLoopTree node, StringBuilder builder) {
        builder.append("循环(");
        scan(node.getCondition(), builder);
        builder.append(")");
        println(builder);
        tab++;
        scan(node.getStatement(), builder);
        tab--;
        printTab(builder);
        builder.append("结束 循环");
        return null;
    }

    @Override
    public Void visitForLoop(ForLoopTree node, StringBuilder builder) {
        builder.append("循环(");
        JavaForLoopDesugar forLoopDesugar = new JavaForLoopDesugar();
        forLoopDesugar.scan(node, null);
        builder.append(forLoopDesugar.getVarName());
        builder.append(", ");
        builder.append(forLoopDesugar.getStartValue());
        builder.append(", ");
        builder.append(forLoopDesugar.getEndValue());
        builder.append(")");
        println(builder);
        tab++;
        scan(node.getStatement(), builder);
        tab--;
        println(builder);
        printTab(builder);
        builder.append("结束 循环");
        return null;
    }

    @Override
    public Void visitEnhancedForLoop(EnhancedForLoopTree node, StringBuilder builder) {
        VariableTree variable = node.getVariable();
        builder.append("循环(");
        scan(node.getExpression(), builder);
        builder.append(" -> ").append(variable.getName());
        builder.append(")");
        tab++;
        println(builder);
        scan(node.getStatement(), builder);
        tab--;
        println(builder);
        printTab(builder);
        builder.append("结束 循环");
        return null;
    }

    @Override
    public Void visitSwitch(SwitchTree node, StringBuilder builder) {
        builder.append("假如 ");
        scan(node.getExpression(), builder);
        tab++;
        for (CaseTree aCase : node.getCases()) {
            println(builder);
            printTab(builder);
            scan(aCase, builder);
        }
        tab--;
        printTab(builder);
        builder.append("结束 假如");
        return null;
    }

    @Override
    public Void visitCase(CaseTree node, StringBuilder builder) {
        builder.append("是 ");
        scan(node.getExpression(), builder);
        println(builder);
        tab++;
        scan(node.getStatements(), builder);
        tab--;
        return null;
    }

    @Override
    public Void visitTry(TryTree node, StringBuilder builder) {
        List<? extends Tree> resources = node.getResources();
        if (resources != null) {
            for (Tree resource : resources) {
                println(builder);
                printTab(builder);
                scan(resource, builder);
            }
        }
        scan(node.getBlock(), builder);
        return null;
    }

    @Override
    public Void visitConditionalExpression(ConditionalExpressionTree node, StringBuilder builder) {
        builder.append("如果 ");
        scan(node.getCondition(), null);
        builder.append(" 则 ");
        scan(node.getTrueExpression(), builder);
        builder.append(" 否则 ");
        scan(node.getFalseExpression(), builder);
        return null;
    }

    @Override
    public Void visitIf(IfTree node, StringBuilder builder) {
        builder.append("如果 ");
        scan(node.getCondition(), builder);
        builder.append(" 则");
        tab++;
        scan(node.getThenStatement(), builder);
        tab--;
        StatementTree elseStatement = node.getElseStatement();
        if (elseStatement != null) {
            println(builder);
            printTab(builder);
            builder.append("否则 ");
            scan(elseStatement, builder);
        }
        println(builder);
        printTab(builder);
        builder.append("结束 如果");
        return null;
    }

    @Override
    public Void visitExpressionStatement(ExpressionStatementTree node, StringBuilder builder) {
        scan(node.getExpression(), builder);
        return null;
    }

    @Override
    public Void visitBreak(BreakTree node, StringBuilder builder) {
        builder.append("退出循环");
        return null;
    }

    @Override
    public Void visitContinue(ContinueTree node, StringBuilder builder) {
        builder.append("跳过循环");
        return null;
    }

    @Override
    public Void visitReturn(ReturnTree node, StringBuilder builder) {
        builder.append("返回 ");
        scan(node.getExpression(), builder);
        return null;
    }

    @Override
    public Void visitMethodInvocation(MethodInvocationTree node, StringBuilder builder) {
        JavaMethodReplacer methodReplacer = new JavaMethodReplacer();
        methodReplacer.scan(node, null);
        if (methodReplacer.getResult() != null) {
            builder.append(methodReplacer.getResult());
        } else {
            scan(node.getMethodSelect(), builder);
        }
        builder.append("(");
        List<? extends ExpressionTree> arguments = node.getArguments();
        for (int i = 0; i < arguments.size(); i++) {
            if (i > 0) {
                builder.append(", ");
            }
            scan(arguments.get(i), builder);
        }
        builder.append(")");
        return null;
    }

    @Override
    public Void visitNewClass(NewClassTree node, StringBuilder builder) {
        builder.append("创建 ");
        ExpressionTree className = node.getIdentifier();
        scan(className, builder);
        /*builder.append("(");
        List<? extends ExpressionTree> arguments = node.getArguments();
        for (int i = 0; i < arguments.size(); i++) {
            if (i > 0) {
                builder.append(", ");
            }
            scan(arguments.get(i), builder);
        }
        builder.append(")");*/
        return null;
    }

    @Override
    public Void visitNewArray(NewArrayTree node, StringBuilder builder) {
        builder.append("{");
        List<? extends ExpressionTree> initializers = node.getInitializers();
        if (initializers != null) {
            for (int i = 0; i < initializers.size(); i++) {
                if (i > 0) {
                    builder.append(", ");
                }
                scan(initializers.get(i), builder);
            }
        }
        builder.append("}");
        return null;
    }

    @Override
    public Void visitParenthesized(ParenthesizedTree node, StringBuilder builder) {
        builder.append("(");
        scan(node.getExpression(), builder);
        builder.append(")");
        return null;
    }

    @Override
    public Void visitAssignment(AssignmentTree node, StringBuilder builder) {
        scan(node.getVariable(), builder);
        builder.append(" = ");
        scan(node.getExpression(), builder);
        return null;
    }

    @Override
    public Void visitUnary(UnaryTree node, StringBuilder builder) {
        JCTree.JCUnary unary = (JCTree.JCUnary) node;
        printOperator(unary.getTag(), builder);
        scan(unary.arg, builder);
        return null;
    }

    @Override
    public Void visitBinary(BinaryTree node, StringBuilder builder) {
        JCTree.JCBinary binary = (JCTree.JCBinary) node;
        scan(binary.lhs, builder);
        builder.append(" ");
        printOperator(binary.getTag(), builder);
        builder.append(" ");
        scan(binary.rhs, builder);
        return null;
    }

    @Override
    public Void visitInstanceOf(InstanceOfTree node, StringBuilder builder) {
        scan(node.getExpression(), builder);
        builder.append(" 属于 ");
        scan(node.getType(), builder);
        return null;
    }

    @Override
    public Void visitArrayAccess(ArrayAccessTree node, StringBuilder builder) {
        scan(node.getExpression(), builder);
        builder.append("[");
        scan(node.getIndex(), builder);
        builder.append("]");
        return null;
    }

    @Override
    public Void visitMemberSelect(MemberSelectTree node, StringBuilder builder) {
        scan(node.getExpression(), builder);
        builder.append(".").append(node.getIdentifier());
        return null;
    }

    @Override
    public Void visitIdentifier(IdentifierTree node, StringBuilder builder) {
        builder.append(node.getName());
        return null;
    }

    @Override
    public Void visitLiteral(LiteralTree node, StringBuilder builder) {
        Object value = node.getValue();
        switch (node.getKind()) {
            case STRING_LITERAL:
                builder.append("\"").append(value).append("\"");
                break;
            case CHAR_LITERAL:
                builder.append("'").append(value).append("'");
                break;
            case NULL_LITERAL:
                builder.append("空");
                break;
            default:
                builder.append(value);
                break;
        }
        return null;
    }

    private void printOperator(JCTree.Tag tag, StringBuilder builder) {
        switch (tag) {
            case PLUS:
                builder.append("+");
                break;
            case MINUS:
                builder.append("-");
                break;
            case MUL:
                builder.append("*");
                break;
            case DIV:
                builder.append("/");
                break;
            case MOD:
                builder.append("%");
                break;
            case EQ:
                builder.append("=");
                break;
            case LT:
                builder.append("<");
                break;
            case GT:
                builder.append(">");
                break;
            case LE:
                builder.append("<=");
                break;
            case GE:
                builder.append(">=");
                break;
            case NE:
                builder.append("~=");
                break;
            default:
                builder.append(tab);
                break;
        }
    }

    private String getTiecodeType(String javaType) {
        return getTiecodeType(javaType, false);
    }

    private String getTiecodeType(String javaType, boolean ignoreArray) {
        String javaClassType = javaType;
        String arraySuffix = null;
        int brackIndex = javaType.indexOf('[');
        if (brackIndex >= 0) {
            arraySuffix = javaType.substring(brackIndex);
            javaClassType = javaType.substring(0, brackIndex);
        }
        Symbol.ClassSymbol classSymbol = elements.getAliasClass(javaClassType);
        if (classSymbol != null) {
            if (arraySuffix == null || ignoreArray) {
                return classSymbol.name.toString();
            } else {
                return classSymbol.name + arraySuffix;
            }
        }
        return javaType;
    }
}
